// Copyright (c) 2022 Presto Labs Pte. Ltd.
// Author: junglekim
#include "cc/appcoin2/support/mm_helper/mm_defender_strategy.h"

namespace coin2::appcoin2::support::mm_helper {

void MMDefenderStrategy::Init(Driver* driver) {
  driver_ = driver;
  auto get_product_holder =
      [this](const coin2::exchange::base::symbology::IProduct& product, int64_t timestamp) {
        return this->driver_->product_holder_manager()->GetProductHolder(product, timestamp);
      };

  auto driverConfig = driver->config();
  CHECK_EQ(driverConfig.order().exchanges().size(), 1);
  CHECK_EQ(driverConfig.order().exchanges().cbegin()->second.products().norms().size(), 1);
  auto symbol_str = driverConfig.order().exchanges().cbegin()->second.products().norms()[0];
  exe_config_.set_mea(driverConfig.order().exchanges().cbegin()->second.mea());
  exe_config_.set_symbol(symbol_str);
  exe_config_.set_lot_size(lot_size_quote_);
  exe_config_.set_order_update_period(order_update_period_);

  if (!start_datetime_.empty()) { timestamp0_ = Readable2Epoch(start_datetime_); }
  if (!end_datetime_.empty()) { timestamp1_ = Readable2Epoch(end_datetime_); }

  // std::vector<double> prices = logspace(log10(sell_min_price_), log10(sell_max_price_), sell_layer_num_);

  // for (const auto& price : prices) {
  //   std::cout << price << ", ";
  // }
  // std:: cout << std::endl;

  auto mea = MarketExchangeApi::FromString(driverConfig.order().exchanges().cbegin()->second.mea());
  executor_ = std::make_unique<LayeringExecutor3>(
      exe_config_,
      driver->order()->GetUniqueSystem(mea),
      std::nullopt,
      nullptr,
      get_product_holder);

  if (buy_min_price_ && buy_max_price_ && buy_layer_num_) {
    done_.first = false;
    const IProductInfo* pi = &(executor_->product_info());
    std::vector<double> buy_prices = logspace(log10(buy_min_price_), log10(buy_max_price_), buy_layer_num_);
    double buy_price_sum = 0;
    for (const auto& buy_price : buy_prices) {
      buy_price_sum += pi->tick_price().RoundDown(buy_price);
    }
    buy_lot_size_ = lot_size_quote_ / buy_price_sum;
    CHECK_GT(buy_lot_size_, pi->tick_qty().submittable_min_qty(buy_prices[0]));
    reverse(buy_prices.begin(), buy_prices.end());
    for (const auto& buy_price : buy_prices) {
      buy_level_.push_back(
        {
          LayeringLevels::POST,
          pi->tick_price().RoundDown(buy_price),
          buy_lot_size_,
          std::nullopt,
          std::nullopt
        }
      );
    }
  }
  if (sell_min_price_ && sell_max_price_ && sell_layer_num_) {
    done_.second = false;
    const IProductInfo* pi = &(executor_->product_info());
    std::vector<double> sell_prices = logspace(log10(sell_min_price_), log10(sell_max_price_), sell_layer_num_);
    double sell_price_sum = 0;
    for (const auto& sell_price : sell_prices) {
      sell_price_sum += pi->tick_price().RoundUp(sell_price);
    }
    sell_lot_size_ = lot_size_quote_ / sell_price_sum;
    CHECK_GT(sell_lot_size_, pi->tick_qty().submittable_min_qty(sell_prices[0]));
    for (const auto& sell_price : sell_prices) {
      sell_level_.push_back(
        {
          LayeringLevels::POST,
          pi->tick_price().RoundUp(sell_price),
          sell_lot_size_,
          std::nullopt,
          std::nullopt
        }
      );
    }    
  }  

  int64_t ts = GetCurrentTimestamp();
  auto product = CreateProductFromNormString(mea, symbol_str, ts);
  product_holder_ = driver->product_holder_manager()->GetProductHolder(*product, ts);
  timer_ = std::make_unique<DeadlineTimer>();
  auto& request = os()->GetAccountRequest();
  owner_ = request.owner();
  timer_->ExpiresEvery(1e9);
}

void MMDefenderStrategy::onCleanupAux(Driver* driver, int num_retries) {
  int num_orders = 0;
  auto ts = GetCurrentTimestamp();
  if (executor_) {
    executor_->SetEnableOrderSubmission(false);
    num_orders += executor_->TryCancelAllWorkingOrders(ts);
  }

  if (num_orders == 0) {
    LOG(INFO) << "Cleared all pending orders.";
    driver->PostTask([driver](){driver->Exit();});
  } else if (num_retries > 10) {
    LOG(WARNING) << "There may be some pendings orders left open.";
    driver->PostTask([driver](){driver->Exit();});
  } else {
    driver->PostDelayedTask(
      [this, driver, num_retries]() {
        this->onCleanupAux(driver, num_retries+1);
      }, 500'000'000LL);
  }
}

void MMDefenderStrategy::onCleanup(Driver* driver) {
  driver->SetImmediateExitOnSignal(false);
  driver->PostTask([this, driver](){ this->onCleanupAux(driver, 0); });
}

void MMDefenderStrategy::onHeartbeatFeed(const FeedTimer& timer) {
  int64_t timestamp = timer.Timestamp();

  if (!os()->is_ready()) return;
  if (!((timestamp0_ < timestamp) && (timestamp < timestamp1_))) {
    executor_->UpdateState(timestamp);
    std::vector<int64_t> proc_order_ids = executor_->GetAllOrderIds();
    for (int64_t proc_order_id : proc_order_ids) {
      os()->gateway()->CancelOrder(proc_order_id);
      std::this_thread::sleep_for(std::chrono::milliseconds(2000));
    }
    if (timer_->Running()) { timer_->Cancel(); }
    LOG(INFO) << fmt::format(
      "Datetime Condition Not Satisfied LB: {} UB {}",
      timestamp0_,
      timestamp1_);
    return;    
  }

  CheckDone(timestamp);
  if (done_.first && done_.second) {
    if (timestamp - last_report_ts_ > 30e9) {
      Report();
      last_report_ts_ = timestamp;
    }
    return;
  }
  ProductEncyclopedia product_cache;
  const IProductInfo* pi = &(executor_->product_info());
  if (!initial_pos_) {
    initial_pos_ = executor_->GetPosition();
    executor_->UpdateMinMaxPosition(
      initial_pos_ - !done_.second * sell_lot_size_ * sell_layer_num_,
      initial_pos_ + !done_.first * buy_lot_size_ * buy_layer_num_,
      timestamp);
    CHECK_GE(initial_pos_, buy_lot_size_ * buy_layer_num_);
    CHECK_GE(initial_pos_, sell_lot_size_ * sell_layer_num_);
  } 
  executor_->UpdateState(timestamp);

  if (!done_.first) executor_->ManageSideOrders(timestamp, 1, LayeringLevels(pi, 1, buy_level_));
  if (!done_.second) executor_->ManageSideOrders(timestamp, -1, LayeringLevels(pi, -1, sell_level_));
}

void MMDefenderStrategy::onAccountOrder(const OrderUpdate& upd) {
  if (upd.product_order_info().event().type() == coin::proto::OrderEvent::ORDER_FILLED) {
    auto oe = upd.product_order_info().event();
    double qty = oe.fill_qty();
    double price = oe.fill_price();

    if (oe.trade_side() == coin::proto::TRADE_BUY_SIDE) {
      buy_fills_[price] += qty;
    } else if (oe.trade_side() == coin::proto::TRADE_SELL_SIDE) {
      sell_fills_[price] += qty;
    }
    executor_->onAccountOrder(upd);
  }
}

void MMDefenderStrategy::onAccountInfo(const OrderUpdate& upd) {
  if (!os()->is_ready()) {
    return;
  }
  executor_->onAccountInfo(upd);
}

void MMDefenderStrategy::Report() {
  nlohmann::json j;
  auto& info = j["INFO"];
  info["SYMBOL"] = symbol_;
  info["OWNER"] = owner_;
  auto& report = j["REPORT"];
  auto& fills = report["FILLED"];
  auto& buy_fills = fills["BUY"];
  auto& sell_fills = fills["SELL"];
  for (auto& [price, qty] : buy_fills_) {
    buy_fills[std::to_string(RoundUp(price * qty, 2))] = RoundUp(price, 6);
  }
  for (auto& [price, qty] : sell_fills_) {
    sell_fills[std::to_string(RoundUp(price * qty, 2))] = RoundUp(price, 6);
  }
  auto& posts = report["POSTED"];
  auto& buy_posts = posts["BUY"];
  auto& sell_posts = posts["SELL"];
  for (const auto& [price, qty] : executor_->GetBuyWorkingOrderPairs()) {
    buy_posts[std::to_string(RoundUp(price * qty, 2))] = RoundUp(price, 6);
  }
  for (const auto& [price, qty] : executor_->GetSellWorkingOrderPairs()) {
    sell_posts[std::to_string(RoundUp(price * qty, 2))] = RoundUp(price, 6);
  }

  SPDLOG_INFO(j.dump());
}

IOrderSubsystem* MMDefenderStrategy::os() {
  auto mea =
      MarketExchangeApi::FromString(driver_->config().order().exchanges().cbegin()->second.mea());
  return driver_->order()->GetUniqueSystem(mea);
}

void MMDefenderStrategy::CheckDone (int64_t timestamp) {
  executor_->UpdateState(timestamp);
  if (executor_->GetBuyWorkingOrderCount() >= buy_layer_num_) { done_.first = true; }
  if (executor_->GetSellWorkingOrderCount() >= sell_layer_num_) { done_.second = true; }
}

int64_t MMDefenderStrategy::Readable2Epoch (std::string readable) {
  struct tm time;
  int64_t result;
  strptime(readable.c_str(), "%Y-%m-%d %H:%M:%S", &time);
  result = (mktime(&time) - timezone) * 1'000'000'000LL;
  return result;
}

std::string MMDefenderStrategy::GetSymbol(const IProduct& product) {
  return product.market_name() + ":" + product.exchange_name() + ":" + product.absolute_norm();
}

double MMDefenderStrategy::RoundUp(double value, int decimal) {
  const double multiplier = std::pow(10.0, decimal);
  return std::ceil(value * multiplier) / multiplier;
}
}  // namespace coin2::appcoin2::support::mm_helper
